package puzzle.slilin2.solver;

import puzzle.common.data.HatanException;
import puzzle.common.point.Direction;
import puzzle.common.point.Pnt;
import puzzle.common.point.Vct;
import puzzle.slilin2.data.CellType;
import puzzle.slilin2.data.LineCellState;
import puzzle.slilin2.data.NumberCellState;
import puzzle.slilin2.data.SlilinBoard;

class IncommingCornerRule extends SlilinCellRule {

	@Override
	protected CellType appliedCellType() {
		return CellType.NUMBER;
	}

	@Override
	protected boolean skipOutside() {
		return false;
	}

	@Override
	protected boolean skipUnmodifiedCell() {
		return true;
	}

	public boolean apply(SlilinBoard b, Pnt p0) throws HatanException {

		boolean isChanged = false;

		isChanged |= processEmpty(b, p0);

		switch (b.getNumber(p0)) {
		case NumberCellState.EMPTY:
			break;
		case 1:
			isChanged |= processOne(b, p0, Direction.N.v);
			isChanged |= processOne(b, p0, Direction.E.v);
			isChanged |= processOne(b, p0, Direction.S.v);
			isChanged |= processOne(b, p0, Direction.W.v);
			break;
		case 2:
			isChanged |= processTwo(b, p0, Direction.N.v);
			isChanged |= processTwo(b, p0, Direction.E.v);
			isChanged |= processTwo(b, p0, Direction.S.v);
			isChanged |= processTwo(b, p0, Direction.W.v);
			break;
		case 3:
			isChanged |= processThree(b, p0, Direction.N.v);
			isChanged |= processThree(b, p0, Direction.E.v);
			isChanged |= processThree(b, p0, Direction.S.v);
			isChanged |= processThree(b, p0, Direction.W.v);
			break;
		}
		return isChanged;
	}

	private boolean processEmpty(SlilinBoard b, Pnt p) throws HatanException {
		// 呼び出し回数が多いので最適化する
		LineCellState s1 = p.y > 0 ? b.getLineCellState(p.x, p.y - 1) : null;
		LineCellState s2 = p.x < b.width - 1 ? b.getLineCellState(p.x + 1, p.y) : null;
		LineCellState s3 = p.y < b.height - 1 ? b.getLineCellState(p.x, p.y + 1) : null;
		LineCellState s4 = p.x > 0 ? b.getLineCellState(p.x - 1, p.y) : null;

		boolean isChanged = false;
		isChanged |= processEmptyAux(b, p, s4, s1, Direction.N.v);
		isChanged |= processEmptyAux(b, p, s1, s2, Direction.E.v);
		isChanged |= processEmptyAux(b, p, s2, s3, Direction.S.v);
		isChanged |= processEmptyAux(b, p, s3, s4, Direction.W.v);
		return isChanged;
	}

	private boolean processEmptyAux(SlilinBoard b, Pnt p0, LineCellState s1, LineCellState s2, Vct v)
			throws HatanException {
		if (s1 != null && s2 != null) {
			if ((s1.isForbidden() && s2.exists()) || (s1.exists() && s2.isForbidden())) {
				return processIncoming(b, p0, v);
			}
		}
		return false;

	}

	private boolean processOne(SlilinBoard b, Pnt p0, Vct v) throws HatanException {
		LineCellState s3 = b.getLineCellState(p0.add(v.inv()));
		LineCellState s4 = b.getLineCellState(p0.add(v.rrot()));
		if (s3.isForbidden() && s4.isForbidden()) {
			return processIncoming(b, p0, v);
		} else {
			return false;
		}
	}

	private boolean processTwo(SlilinBoard b, Pnt p0, Vct v) throws HatanException {
		boolean isChanged = false;

		Vct vl = v.lrot();
		Vct vr = v.rrot();
		Vct vi = v.inv();

		LineCellState s1 = b.getLineCellState(p0.add(vi, 2).add(vl));
		LineCellState s2 = b.getLineCellState(p0.add(vl, 2).add(vi));
		if (s1.isForbidden() && s2.isForbidden()) {
			isChanged |= processIncoming(b, p0, v);
		}

		LineCellState s3 = b.getLineCellState(p0.add(v, 2).add(vr));
		LineCellState s4 = b.getLineCellState(p0.add(vr, 2).add(v));
		if (s3.isForbidden() && s4.isForbidden()) {
			isChanged |= processIncoming(b, p0, v);
		}

		return isChanged;
	}

	private boolean processThree(SlilinBoard b, Pnt p0, Vct v) throws HatanException {
		LineCellState s1 = b.getLineCellState(p0.add(v.inv()));
		LineCellState s2 = b.getLineCellState(p0.add(v.rrot()));
		if (s1.exists() && s2.exists()) {
			return processIncoming(b, p0, v);
		}
		return false;
	}

	private boolean processIncoming(SlilinBoard b, Pnt p0, Vct v) throws HatanException {
		Pnt p1 = p0.add(v, 2).add(v.lrot(), 2);

		LineCellState s1 = b.getLineCellState(p1.add(v.inv()));
		LineCellState s2 = b.getLineCellState(p1.add(v.rrot()));

		boolean isChanged = false;
		if (s1.isForbidden()) {
			if (s2.isForbidden()) throw new HatanException();
			isChanged |= s2.setExists(true);
		} else if (s2.isForbidden()) {
			if (s1.isForbidden()) throw new HatanException();
			isChanged |= s1.setExists(true);
		} else if (s1.exists()) {
			if (s2.exists()) throw new HatanException();
			isChanged |= s2.setForbidden(true);
		} else if (s2.exists() && !s1.isFixed()) {
			if (s1.exists()) throw new HatanException();
			isChanged |= s1.setForbidden(true);
		}

		switch (b.getNumber(p1)) {
		case 1:
			isChanged |= b.getLineCellState(p1.add(v)).setForbidden(true);
			isChanged |= b.getLineCellState(p1.add(v.lrot())).setForbidden(true);
			break;
		case 2:
			isChanged |= processIncoming(b, p1, v);
			break;
		case 3:
			isChanged |= b.getLineCellState(p1.add(v)).setExists(true);
			isChanged |= b.getLineCellState(p1.add(v.lrot())).setExists(true);
			break;
		}
		return isChanged;
	}

}
